// Copyright 2019 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <memory>

#include "deps_log.h"
#include "graph.h"
#include "missing_deps.h"
#include "state.h"
#include "test.h"

const char kTestDepsLogFilename[] = "MissingDepTest-tempdepslog";

class MissingDependencyTestDelegate : public MissingDependencyScannerDelegate {
    void OnMissingDep(Node* node, const std::string& path,
                                        const Rule& generator) {}
};

struct MissingDependencyScannerTest : public testing::Test {
    MissingDependencyScannerTest()
            : generator_rule_("generator_rule"), compile_rule_("compile_rule"),
                scanner_(&delegate_, &deps_log_, &state_, &filesystem_) {
        std::string err;
        deps_log_.OpenForWrite(kTestDepsLogFilename, &err);
        EXPECT_EQ("", err);
    }

    ~MissingDependencyScannerTest() {
        // Remove test file.
        deps_log_.Close();
    }

    MissingDependencyScanner& scanner() { return scanner_; }

    void RecordDepsLogDep(const std::string& from, const std::string& to) {
        Node* node_deps[] = { state_.LookupNode(to) };
        deps_log_.RecordDeps(state_.LookupNode(from), 0, 1, node_deps);
    }

    void ProcessAllNodes() {
        std::string err;
        std::vector<Node*> nodes = state_.RootNodes(&err);
        EXPECT_EQ("", err);
        for (std::vector<Node*>::iterator it = nodes.begin(); it != nodes.end();
                  ++it) {
            scanner().ProcessNode(*it);
        }
    }

    void CreateInitialState() {
        EvalString deps_type;
        deps_type.AddText("gcc");
        compile_rule_.AddBinding("deps", deps_type);
        generator_rule_.AddBinding("deps", deps_type);
        Edge* header_edge = state_.AddEdge(&generator_rule_);
        state_.AddOut(header_edge, "generated_header", 0);
        Edge* compile_edge = state_.AddEdge(&compile_rule_);
        state_.AddOut(compile_edge, "compiled_object", 0);
    }

    void CreateGraphDependencyBetween(const char* from, const char* to) {
        Node* from_node = state_.LookupNode(from);
        Edge* from_edge = from_node->in_edge();
        state_.AddIn(from_edge, to, 0);
    }

    void AssertMissingDependencyBetween(const char* flaky, const char* generated,
                                                                            Rule* rule) {
        Node* flaky_node = state_.LookupNode(flaky);
        ASSERT_EQ(1u, scanner().nodes_missing_deps_.count(flaky_node));
        Node* generated_node = state_.LookupNode(generated);
        ASSERT_EQ(1u, scanner().generated_nodes_.count(generated_node));
        ASSERT_EQ(1u, scanner().generator_rules_.count(rule));
    }

    ScopedFilePath scoped_file_path_ = kTestDepsLogFilename;
    MissingDependencyTestDelegate delegate_;
    Rule generator_rule_;
    Rule compile_rule_;
    DepsLog deps_log_;
    State state_;
    VirtualFileSystem filesystem_;
    MissingDependencyScanner scanner_;
};

TEST_F(MissingDependencyScannerTest, EmptyGraph) {
    ProcessAllNodes();
    ASSERT_FALSE(scanner().HadMissingDeps());
}

TEST_F(MissingDependencyScannerTest, NoMissingDep) {
    CreateInitialState();
    ProcessAllNodes();
    ASSERT_FALSE(scanner().HadMissingDeps());
}

TEST_F(MissingDependencyScannerTest, MissingDepPresent) {
    CreateInitialState();
    // compiled_object uses generated_header, without a proper dependency
    RecordDepsLogDep("compiled_object", "generated_header");
    ProcessAllNodes();
    ASSERT_TRUE(scanner().HadMissingDeps());
    ASSERT_EQ(1u, scanner().nodes_missing_deps_.size());
    ASSERT_EQ(1u, scanner().missing_dep_path_count_);
    AssertMissingDependencyBetween("compiled_object", "generated_header",
                                                                  &generator_rule_);
}

TEST_F(MissingDependencyScannerTest, MissingDepFixedDirect) {
    CreateInitialState();
    // Adding the direct dependency fixes the missing dep
    CreateGraphDependencyBetween("compiled_object", "generated_header");
    RecordDepsLogDep("compiled_object", "generated_header");
    ProcessAllNodes();
    ASSERT_FALSE(scanner().HadMissingDeps());
}

TEST_F(MissingDependencyScannerTest, MissingDepFixedIndirect) {
    CreateInitialState();
    // Adding an indirect dependency also fixes the issue
    Edge* intermediate_edge = state_.AddEdge(&generator_rule_);
    state_.AddOut(intermediate_edge, "intermediate", 0);
    CreateGraphDependencyBetween("compiled_object", "intermediate");
    CreateGraphDependencyBetween("intermediate", "generated_header");
    RecordDepsLogDep("compiled_object", "generated_header");
    ProcessAllNodes();
    ASSERT_FALSE(scanner().HadMissingDeps());
}

TEST_F(MissingDependencyScannerTest, CyclicMissingDep) {
    CreateInitialState();
    RecordDepsLogDep("generated_header", "compiled_object");
    RecordDepsLogDep("compiled_object", "generated_header");
    // In case of a cycle, both paths are reported (and there is
    // no way to fix the issue by adding deps).
    ProcessAllNodes();
    ASSERT_TRUE(scanner().HadMissingDeps());
    ASSERT_EQ(2u, scanner().nodes_missing_deps_.size());
    ASSERT_EQ(2u, scanner().missing_dep_path_count_);
    AssertMissingDependencyBetween("compiled_object", "generated_header",
                                                                  &generator_rule_);
    AssertMissingDependencyBetween("generated_header", "compiled_object",
                                                                  &compile_rule_);
}

TEST_F(MissingDependencyScannerTest, CycleInGraph) {
    CreateInitialState();
    CreateGraphDependencyBetween("compiled_object", "generated_header");
    CreateGraphDependencyBetween("generated_header", "compiled_object");
    // The missing-deps tool doesn't deal with cycles in the graph, because
    // there will be an error loading the graph before we get to the tool.
    // This test is to illustrate that.
    std::string err;
    std::vector<Node*> nodes = state_.RootNodes(&err);
    ASSERT_NE("", err);
}
